namespace AgentJohnson
{
  using System;
  using JetBrains.Annotations;
  using JetBrains.ReSharper.Feature.Services.CSharp.Bulbs;
  using Resharper.Abstractions.Actions;
  using Resharper.Abstractions.CodeDom.Expressions;
  using Resharper.Abstractions.CodeDom.Expressions.BinaryExpressions.EqualityExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.BinaryExpressions.RelationalExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.InvocationExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.LiteralExpressions;
  using Resharper.Abstractions.CodeDom.Expressions.UnaryOperatorExpressions;
  using Resharper.Abstractions.CodeDom.Statements.IfStatements;

  /// <summary>Defines the <see cref="NegateIfCondition"/> class.</summary>
  [ContextAction("Negate 'if' condition [Agent Johnson]", "Negates the condition of an 'if' statement.")]
  public class NegateIfCondition : ContextActionBase
  {
    #region Constructors and Destructors

    /// <summary>Initializes a new instance of the <see cref="NegateIfCondition"/> class.</summary>
    /// <param name="provider">The provider.</param>
    public NegateIfCondition([NotNull] ICSharpContextActionDataProvider provider) : base(provider)
    {
    }

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified action context.</summary>
    /// <param name="context">The action context.</param>
    /// <returns><c>true</c> if this instance can execute the specified action context; otherwise, <c>false</c>.</returns>
    protected override bool CanExecute(CanExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      return this.GetModel(context) != null;
    }

    /// <summary>Executes the action.</summary>
    /// <param name="context">The execute context.</param>
    protected override void Execute(ExecuteContext context)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      var model = this.GetModel(context);
      if (model == null)
      {
        return;
      }

      var condition = model.Condition;

      var equalityExpression = condition as EqualityExpression;
      if (equalityExpression != null)
      {
        NegateEqualityExpression(context, equalityExpression);
        return;
      }

      var relationalExpression = condition as RelationalExpression;
      if (relationalExpression != null)
      {
        this.NegateRelationalExpression(context, relationalExpression);
        return;
      }

      var unaryOperatorExpression = condition as UnaryOperatorExpression;
      if (unaryOperatorExpression != null)
      {
        this.NegateUnaryExpression(context, unaryOperatorExpression);
        return;
      }

      var invocationExpression = condition as InvocationExpression;
      if (invocationExpression != null)
      {
        this.NegateInvocationExpression(context, invocationExpression);
        return;
      }

      var literalExpression = condition as LiteralExpression;
      if (literalExpression != null)
      {
        this.NegateLiteralExpression(context, literalExpression);
        return;
      }

      this.NegateExpression(context, condition);
    }

    /// <summary>Gets the text.</summary>
    /// <returns>Returns the text.</returns>
    protected override string GetText()
    {
      return "Negate 'if' condition [Agent Johnson]";
    }

    /// <summary>Negates the equality expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="equalityExpression">The equality expression.</param>
    private static void NegateEqualityExpression([NotNull] ExecuteContext context, [NotNull] EqualityExpression equalityExpression)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (equalityExpression == null)
      {
        throw new ArgumentNullException("equalityExpression");
      }

      var operatorSign = equalityExpression.OperatorSign;

      operatorSign = operatorSign == "==" ? "!=" : "==";

      var expression = context.Factory.CreateExpression(string.Format("{0} {1} {2}", equalityExpression.LeftOperand, operatorSign, equalityExpression.RightOperand));

      equalityExpression.ReplaceBy(expression);
    }

    /// <summary>Gets the model.</summary>
    /// <param name="context">The context.</param>
    /// <returns>Returns the model.</returns>
    private Model GetModel(ProviderContext context)
    {
      var ifStatement = context.GetSelectedIfStatement();
      if (ifStatement == null)
      {
        return null;
      }

      var condition = ifStatement.Condition;
      if (condition == null)
      {
        return null;
      }

      if (!condition.ContainsCaretOffset(context.CaretOffset))
      {
        return null;
      }

      return new Model
      {
        Condition = condition
      };
    }

    /// <summary>Negates the expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="condition">The condition.</param>
    private void NegateExpression([NotNull] ExecuteContext context, [NotNull] Expression condition)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (condition == null)
      {
        throw new ArgumentNullException("condition");
      }

      condition.ReplaceBy(context.Factory.CreateExpression("!(" + condition + ")"));
    }

    /// <summary>Negates the invocation expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="invocationExpression">The invocation expression.</param>
    private void NegateInvocationExpression([NotNull] ExecuteContext context, [NotNull] InvocationExpression invocationExpression)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (invocationExpression == null)
      {
        throw new ArgumentNullException("invocationExpression");
      }

      invocationExpression.ReplaceBy(context.Factory.CreateExpression("!" + invocationExpression));
    }

    /// <summary>Negates the literal expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="literalExpression">The literal expression.</param>
    private void NegateLiteralExpression([NotNull] ExecuteContext context, [NotNull] LiteralExpression literalExpression)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (literalExpression == null)
      {
        throw new ArgumentNullException("literalExpression");
      }

      var text = literalExpression.ToString();

      if (text == "true")
      {
        text = "false";
      }
      else if (text == "false")
      {
        text = "true";
      }
      else
      {
        return;
      }

      var expression = context.Factory.CreateExpression(text);

      literalExpression.ReplaceBy(expression);
    }

    /// <summary>Negates the relational expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="relationalExpression">The relational expression.</param>
    private void NegateRelationalExpression([NotNull] ExecuteContext context, [NotNull] RelationalExpression relationalExpression)
    {
      if (context == null)
      {
        throw new ArgumentNullException("context");
      }

      if (relationalExpression == null)
      {
        throw new ArgumentNullException("relationalExpression");
      }

      var operatorSign = relationalExpression.OperatorSign;
      switch (operatorSign)
      {
        case "<":
          operatorSign = ">=";
          break;
        case ">":
          operatorSign = "<=";
          break;
        case "<=":
          operatorSign = ">";
          break;
        case ">=":
          operatorSign = "<";
          break;
      }

      var expression = context.Factory.CreateExpression(string.Format("{0} {1} {2}", relationalExpression.LeftOperand, operatorSign, relationalExpression.RightOperand));

      relationalExpression.ReplaceBy(expression);
    }

    /// <summary>Negates the unary expression.</summary>
    /// <param name="context">The context.</param>
    /// <param name="unaryOperatorExpression">The unary operator expression.</param>
    private void NegateUnaryExpression(ExecuteContext context, UnaryOperatorExpression unaryOperatorExpression)
    {
      if (unaryOperatorExpression.OperatorSign != "!")
      {
        return;
      }

      var operand = unaryOperatorExpression.Operand;
      if (operand == null)
      {
        return;
      }

      var text = operand.ToString().Trim();

      if (text.StartsWith("(") && text.EndsWith(")"))
      {
        text = text.Substring(1, text.Length - 2);
      }

      unaryOperatorExpression.ReplaceBy(context.Factory.CreateExpression(text));
    }

    #endregion

    /// <summary>Defines the <see cref="Model"/> class.</summary>
    private class Model
    {
      #region Properties

      /// <summary>
      /// Gets or sets the condition.
      /// </summary>
      /// <value>The condition.</value>
      [NotNull]
      public Expression Condition { get; set; }

      #endregion
    }
  }
}